package com.hk.core.test.mockito;

import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.mockito.Mockito;
import org.springframework.data.geo.Circle;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.Point;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.connection.RedisGeoCommands.GeoLocation;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.domain.geo.BoundingBox;
import org.springframework.data.redis.domain.geo.GeoReference;
import org.springframework.data.redis.domain.geo.GeoShape;

import java.time.Duration;
import java.time.Instant;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.concurrent.TimeUnit;

import static org.mockito.ArgumentMatchers.*;

@SuppressWarnings({"unchecked"})
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public abstract class RedisTemplateMockito {

    /**
     * mockito StringRedisTemplate
     */
    public static StringRedisTemplate mockitoStringRedisTemplate() {
        StringRedisTemplate stringRedisTemplate = Mockito.mock(StringRedisTemplate.class);
        mockitoConnection(stringRedisTemplate);
        redisTemplateListMockito(stringRedisTemplate);
        redisTemplateValueMockito(stringRedisTemplate);
        redisTemplateHashMockito(stringRedisTemplate);
        redisTemplateSetMockito(stringRedisTemplate);
        redisTemplateZSetMockito(stringRedisTemplate);
        redisTemplateGeoMockito(stringRedisTemplate);
        redisTemplateDeleteAndExpireMockito(stringRedisTemplate);
        return stringRedisTemplate;
    }

    /**
     * mockito RedisTemplate
     */
    public static <K, V> RedisTemplate<K, V> mockitoRedisTemplate() {
        RedisTemplate<K, V> redisTemplate = Mockito.mock(RedisTemplate.class);
        mockitoConnection(redisTemplate);
        redisTemplateListMockito(redisTemplate);
        redisTemplateValueMockito(redisTemplate);
        redisTemplateHashMockito(redisTemplate);
        redisTemplateSetMockito(redisTemplate);
        redisTemplateZSetMockito(redisTemplate);
        redisTemplateGeoMockito(redisTemplate);
        redisTemplateDeleteAndExpireMockito(redisTemplate);
        return redisTemplate;
    }

    public static <K, V> void mockitoConnection(RedisTemplate<K, V> redisTemplate) {
        RedisConnectionFactory connectionFactory = Mockito.mock(RedisConnectionFactory.class);
        Mockito.when(redisTemplate.getConnectionFactory()).thenReturn(connectionFactory);
        Mockito.when(redisTemplate.getRequiredConnectionFactory()).thenReturn(connectionFactory);
        RedisConnection redisConnection = Mockito.mock(RedisConnection.class);
        Mockito.when(connectionFactory.getConnection()).thenReturn(redisConnection);
    }

    public static <K, V> void redisTemplateListMockito(RedisTemplate<K, V> redisTemplate) {
        ListOperations<K, V> operations = Mockito.spy(ListOperations.class);
        Mockito.when(redisTemplate.opsForList()).thenReturn(operations);
        Mockito.when(operations.leftPush(any(), any())).thenReturn(null);
        Mockito.when(operations.leftPushAll(any(), (V) any(Object[].class))).thenReturn(null);
        Mockito.when(operations.leftPushAll(any(), anyCollection())).thenReturn(null);
        Mockito.when(operations.leftPushIfPresent(any(), any())).thenReturn(null);
        Mockito.when(operations.leftPush(any(), any(), any())).thenReturn(null);

        Mockito.when(operations.rightPush(any(), any())).thenReturn(null);
        Mockito.when(operations.rightPushAll(any(), (V) any(Object[].class))).thenReturn(null);
        Mockito.when(operations.rightPushAll(any(), anyCollection())).thenReturn(null);
        Mockito.when(operations.rightPushIfPresent(any(), any())).thenReturn(null);
        Mockito.when(operations.rightPush(any(), any(), any())).thenReturn(null);

        Mockito.when(operations.move(any(), any())).thenReturn(null);
        Mockito.when(operations.move(any(), any(), any())).thenReturn(null);
        Mockito.when(operations.move(any(), any(), any(), any())).thenReturn(null);
        Mockito.when(operations.move(any(), any(), any(), any(), any())).thenReturn(null);
        Mockito.when(operations.move(any(), any(), any(), any(), anyLong(), any())).thenReturn(null);

        Mockito.doNothing().when(operations).set(any(), anyLong(), any());
        Mockito.when(operations.remove(any(), anyLong(), any())).thenReturn(null);

        Mockito.when(operations.leftPop(any())).thenReturn(null);
        Mockito.when(operations.leftPop(any(), anyLong())).thenReturn(Collections.emptyList());
        Mockito.when(operations.leftPop(any(), anyLong(), any(TimeUnit.class))).thenReturn(null);
        Mockito.when(operations.leftPop(any(), any(Duration.class))).thenReturn(null);

        Mockito.when(operations.rightPop(any())).thenReturn(null);
        Mockito.when(operations.rightPop(any(), anyLong())).thenReturn(Collections.emptyList());
        Mockito.when(operations.rightPop(any(), anyLong(), any(TimeUnit.class))).thenReturn(null);
        Mockito.when(operations.rightPop(any(), any(Duration.class))).thenReturn(null);

        Mockito.when(operations.rightPopAndLeftPush(any(), any())).thenReturn(null);
        Mockito.when(operations.rightPopAndLeftPush(any(), any(), anyLong(), any(TimeUnit.class))).thenReturn(null);
        Mockito.when(operations.rightPopAndLeftPush(any(), any(), any(Duration.class))).thenReturn(null);
    }

    public static <K, V> void redisTemplateValueMockito(RedisTemplate<K, V> redisTemplate) {
        ValueOperations<K, V> operations = Mockito.spy(ValueOperations.class);
        Mockito.when(redisTemplate.opsForValue()).thenReturn(operations);
        Mockito.doNothing().when(operations).set(any(), any());
        Mockito.doNothing().when(operations).set(any(), any(), anyLong(), any(TimeUnit.class));
        Mockito.doNothing().when(operations).set(any(), any(), any(Duration.class));

        Mockito.when(operations.setIfAbsent(any(), any(), any(Duration.class))).thenReturn(null);
        Mockito.when(operations.setIfAbsent(any(), any())).thenReturn(null);
        Mockito.when(operations.setIfAbsent(any(), any(), anyLong(), any(TimeUnit.class))).thenReturn(null);
        Mockito.doNothing().when(operations).multiSet(anyMap());
        Mockito.when(operations.multiSetIfAbsent(anyMap())).thenReturn(null);

        Mockito.when(operations.getAndDelete(any())).thenReturn(null);
        Mockito.when(operations.getAndExpire(any(), any())).thenReturn(null);
        Mockito.when(operations.getAndExpire(any(), any(), any())).thenReturn(null);
        Mockito.when(operations.getAndPersist(any())).thenReturn(null);
        Mockito.when(operations.getAndSet(any(), any())).thenReturn(null);
        Mockito.when(operations.increment(any())).thenReturn(null);
        Mockito.when(operations.increment(any(), anyLong())).thenReturn(null);
        Mockito.when(operations.increment(any(), anyDouble())).thenReturn(null);
        Mockito.when(operations.decrement(any())).thenReturn(null);
        Mockito.when(operations.decrement(any(), anyLong())).thenReturn(null);
        Mockito.when(operations.append(any(), any())).thenReturn(null);
        Mockito.doNothing().when(operations).set(any(), any(), anyLong());
        Mockito.when(operations.setBit(any(), anyLong(), anyBoolean())).thenReturn(null);
        Mockito.when(operations.getAndDelete(any())).thenReturn(null);
    }

    public static <K, V> void redisTemplateHashMockito(RedisTemplate<K, V> redisTemplate) {
        HashOperations<K, Object, Object> operations = Mockito.spy(HashOperations.class);
        Mockito.when(redisTemplate.opsForHash()).thenReturn(operations);
        Mockito.when(operations.delete(any(), any())).thenReturn(null);
        Mockito.when(operations.increment(any(), any(), anyLong())).thenReturn(null);
        Mockito.when(operations.increment(any(), any(), anyDouble())).thenReturn(null);

        Mockito.doNothing().when(operations).putAll(any(), anyMap());
        Mockito.doNothing().when(operations).put(any(), any(), any());
        Mockito.doNothing().when(operations).putIfAbsent(any(), any(), any());
    }

    public static <K, V> void redisTemplateSetMockito(RedisTemplate<K, V> redisTemplate) {
        SetOperations<K, V> operations = Mockito.spy(SetOperations.class);
        Mockito.when(redisTemplate.opsForSet()).thenReturn(operations);
        Mockito.when(operations.add(any(), any())).thenReturn(null);
        Mockito.when(operations.remove(any(), any())).thenReturn(null);
        Mockito.when(operations.pop(any())).thenReturn(null);
        Mockito.when(operations.move(any(), any(), any())).thenReturn(null);
        Mockito.when(operations.intersectAndStore(any(), (K) any(), any())).thenReturn(null);
        Mockito.when(operations.intersectAndStore(anyCollection(), any())).thenReturn(null);
        Mockito.when(operations.unionAndStore(anyCollection(), any())).thenReturn(null);
        Mockito.when(operations.differenceAndStore(anyCollection(), any())).thenReturn(null);
    }

    public static <K, V> void redisTemplateZSetMockito(RedisTemplate<K, V> redisTemplate) {
        ZSetOperations<K, V> operations = Mockito.spy(ZSetOperations.class);
        Mockito.when(redisTemplate.opsForZSet()).thenReturn(operations);
        Mockito.when(operations.add(any(), any(), anyDouble())).thenReturn(null);
        Mockito.when(operations.addIfAbsent(any(), any(), anyDouble())).thenReturn(null);
        Mockito.when(operations.add(any(), anySet())).thenReturn(null);
        Mockito.when(operations.addIfAbsent(any(), anySet())).thenReturn(null);
        Mockito.when(operations.remove(any(), any())).thenReturn(null);
        Mockito.when(operations.incrementScore(any(), any(), anyDouble())).thenReturn(null);
    }

    public static <K, V> void redisTemplateGeoMockito(RedisTemplate<K, V> redisTemplate) {
        GeoOperations<K, V> operations = Mockito.spy(GeoOperations.class);
        Mockito.when(redisTemplate.opsForGeo()).thenReturn(operations);
        Mockito.when(operations.add(any(), any(Point.class), any())).thenReturn(null);
        Mockito.when(operations.add(any(), any(GeoLocation.class))).thenReturn(null);
        Mockito.when(operations.add(any(), anyMap())).thenReturn(null);
        Mockito.when(operations.add(any(), anyIterable())).thenReturn(null);
        Mockito.when(operations.remove(any(), any())).thenReturn(null);
        Mockito.when(operations.searchAndStore(any(), any(), any(Circle.class))).thenReturn(null);
        Mockito.when(operations.searchAndStore(any(), any(), any(GeoReference.class), any(Distance.class))).thenReturn(null);
        Mockito.when(operations.searchAndStore(any(), any(), any(GeoReference.class), any(Distance.class), any(RedisGeoCommands.GeoSearchStoreCommandArgs.class))).thenReturn(null);
        Mockito.when(operations.searchAndStore(any(), any(), any(GeoReference.class), any(BoundingBox.class))).thenReturn(null);
        Mockito.when(operations.searchAndStore(any(), any(), any(GeoReference.class), any(BoundingBox.class), any(RedisGeoCommands.GeoSearchStoreCommandArgs.class))).thenReturn(null);
        Mockito.when(operations.searchAndStore(any(), any(), any(GeoReference.class), any(GeoShape.class), any(RedisGeoCommands.GeoSearchStoreCommandArgs.class))).thenReturn(null);
    }

    public static <K, V> void redisTemplateDeleteAndExpireMockito(RedisTemplate<K, V> redisTemplate) {
        Mockito.when(redisTemplate.hasKey(any())).thenReturn(false);
        Mockito.when(redisTemplate.delete((K) any(Object.class))).thenReturn(null);
        Mockito.when(redisTemplate.delete(any(Collection.class))).thenReturn(null);
        Mockito.when(redisTemplate.expire(any(), anyLong(), any())).thenReturn(null);
        Mockito.when(redisTemplate.expire(any(), any())).thenReturn(null);
        Mockito.when(redisTemplate.expireAt(any(), any(Date.class))).thenReturn(null);
        Mockito.when(redisTemplate.expireAt(any(), any(Instant.class))).thenReturn(null);
    }
}
